/**
 * 
 */
package ex3.taskqueue;

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.util.LinkedList;

import ex3.executor.Executor;

/**
 * @author Omer
 *
 */

public class OpQueue implements Runnable {
	
	
	public OpQueue(Character opCharacter){
		opChar = opCharacter;
		taskList = new LinkedList<TaskInfo>();
		numberOfRunningTasks = new Integer(0);
		numberOfRunningTasksLock = new Object();
	}
	
	//	OpQueue's operation
	public void run() {
		
		//	Do until all tasks are done and file reading is finished
		//while (!TaskQueueImpl.taskQueue.FinishedReadingInput() || !IsEmptyQueueSync()){
		while (true){
			
			while (IsEmptyQueueSync()){
				//	If queue is empty and no more messages are left, we've finished this OpQueue
				
				//	If there are running tasks we wait till all of them will finish
				synchronized (numberOfRunningTasksLock) {
					if (numberOfRunningTasks > 0){
						try {
								numberOfRunningTasksLock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
				
				//	Finished all tasks - both from file and failed ones
				if (TaskQueueImpl.taskQueue.FinishedReadingInput() && IsEmptyQueueSync())
				{
					TaskQueueImpl.taskQueue.RemoveTaskQueue(opChar);
				//	System.out.println("++++++++++++++++++ Finished " + opChar + " ++++++++++++++++++++++");
					TaskQueueImpl.taskQueue.DestroyThread();
					return;
				}
				
				//	If the queue is empty, wait on it for an add task wakeup  
				//	(this can happen when the file is still reading)
				try {	
					synchronized (taskList) {
						if (IsEmptyQueueSync()){
							taskList.wait();
						}
					}
				} catch (InterruptedException e) {
					System.out.println("Interrupted.");
					e.printStackTrace();
				}
			}
			
			//	Now we have tasks
			//	Getting the last task
			TaskInfo task;
			synchronized (taskList) {
				task = taskList.poll();
			}
			
			//	Getting executor
			String exName = null;
			while (exName == null){
				//	Try to get an executor from the MatchMaker
				synchronized (this) {
					try {
						exName = TaskQueueImpl.matchmakerRef.getExecutor(task.op, TaskQueueImpl.taskQueue.taskQueueRMIName);
					} catch (RemoteException e) {
						System.out.println("Remote exception while getting an executor.");
						e.printStackTrace();
						System.exit(1);
					}
					if (exName == null){	//	No executors in Matchmaker - go to sleep
						try {	//	Taking OpQueue's lock and waiting on it
						//	System.out.print(".");
							wait();
						} catch (InterruptedException e) {
							System.out.println("Interrupted.");
							e.printStackTrace();
						}
					}
				}
			}
			
			//	Sending it to another thread to execute
			//	**************************************************
			//	Getting the executor from the RMI
			Executor ex = null;
			try { 
	            ex = (Executor)Naming.lookup("//" +  TaskQueueImpl.taskQueue.matchmakerIP + ":" + 
	            									 TaskQueueImpl.taskQueue.matchmakerRegPort + "/" + exName);
	            if (ex == null){
					System.out.println("Error - executor retrived is null.");
		            System.out.println("Exiting.");  
		            System.exit(1);
				}
			} catch (Exception e) { 
	            System.out.println("OpQueue failed to find executor: " + e.getMessage());
	            System.out.println("Exiting."); 
	            e.printStackTrace(); 
	            System.exit(1);
	        } 
			
			//	Running the op in a new thread
			synchronized (numberOfRunningTasksLock) {
				numberOfRunningTasks++;
			}
			TaskQueueImpl.taskQueue.AddThread();
			TaskQueueImpl.taskQueue.RunOp(ex, task.op, task.taskID, task.op1, task.op2, this);
		}
	}
	
	//	used to notify the queue that a task was completed
	public void FinishedATask(){
		synchronized (numberOfRunningTasksLock) {
			numberOfRunningTasks--;
			
			if (numberOfRunningTasks == 0){
				numberOfRunningTasksLock.notify();
			}	
		}
	}
	
	//	Adding tasks to this op
	public void AddTask(int TaskID, int op1, int op2, boolean addToFront) {
		
		synchronized (taskList) {
			if (addToFront)
				taskList.addFirst(new TaskInfo(TaskID, opChar, op1, op2));
			else
				taskList.addLast(new TaskInfo(TaskID, opChar, op1, op2));
		
			//	Notify the sleeper
			taskList.notify();
		}
	}
	
	//	Checking if the queue is empty using lock
	private boolean IsEmptyQueueSync(){
		synchronized (taskList) {
			return taskList.isEmpty();
		}
	}
	
	private Integer numberOfRunningTasks;
	private Character opChar;
	private LinkedList<TaskInfo> taskList;
	private Object numberOfRunningTasksLock;
}
